﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using ParserGenerator.Extensions;
using ParserGenerator.GrammarConstruction;

namespace ParserGenerator.ParserConstruction
{
    [Serializable]
    public sealed class ParserAutomatonInadequacyNodesTable : ReadOnlyDictionary<ParserAutomatonState, ParserAutomatonInadequacyNodes>
    {
        [NonSerialized]
        private ReadOnlyDictionary<GrammarTerminalSymbol, ParserAutomatonInadequacyNodes> _groupedByTokenWrapper;
        private readonly Dictionary<GrammarTerminalSymbol, ParserAutomatonInadequacyNodes> _groupedByToken;
        public ReadOnlyDictionary<GrammarTerminalSymbol, ParserAutomatonInadequacyNodes> GroupedByToken
        {
            get
            {
                if (_groupedByTokenWrapper == null)
                    _groupedByTokenWrapper = _groupedByToken.AsReadOnly();
                return _groupedByTokenWrapper;
            }
        }

        internal ParserAutomatonInadequacyNodesTable()
            : base(new Dictionary<ParserAutomatonState, ParserAutomatonInadequacyNodes>())
        {
            _groupedByToken = new Dictionary<GrammarTerminalSymbol, ParserAutomatonInadequacyNodes>();
        }

        internal ParserAutomatonInadequacyNodesTable(ParserAutomatonActionsTable actions)
            : this()
        {
            if (actions == null)
                throw new ArgumentNullException(nameof(actions));
            int number = 0;
            foreach (var t in actions)
            {
                foreach (ParserAutomatonAction action in t.Value.Values)
                {
                    if (!action.IsAmbiguous)
                        continue;
                    var node = new ParserAutomatonInadequacyNode(number++, action);
                    Add(t.Key, node);
                }
            }
        }

        internal void Add(ParserAutomatonState state, ParserAutomatonInadequacyNode node)
        {
            if (state == null)
                throw new ArgumentNullException(nameof(state));
            if (node == null)
                throw new ArgumentNullException(nameof(node));
            ParserAutomatonInadequacyNodes nodes;
            if (!Dictionary.TryGetValue(state, out nodes))
            {
                nodes = new ParserAutomatonInadequacyNodes();
                Dictionary.Add(state, nodes);
            }
            nodes.Add(node);
            ParserAutomatonInadequacyNodes list;
            if (!_groupedByToken.TryGetValue(node.ConflictedToken, out list))
            {
                list = new ParserAutomatonInadequacyNodes();
                _groupedByToken.Add(node.ConflictedToken, list);
            }
            list.Add(node);
        }

        public new ParserAutomatonInadequacyNodes this[ParserAutomatonState state]
        {
            get
            {
                if (state == null)
                    throw new ArgumentNullException(nameof(state));
                ParserAutomatonInadequacyNodes nodes;
                if (!Dictionary.TryGetValue(state, out nodes))
                {
                    nodes = new ParserAutomatonInadequacyNodes();
                    Dictionary.Add(state, nodes);
                }
                return nodes;
            }
        }

        public sealed override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            foreach (var t in _groupedByToken)
            {
                sb.Append(t.Key);
                sb.AppendFormat("\n{0}", new string('-', 40));
                foreach (ParserAutomatonInadequacyNode node in t.Value)
                {
                    sb.AppendFormat("\n{0}:", node.ManifestingState);
                    foreach (ParserAutomatonContribution c in node.Action)
                        if (c.IsReduction)
                            sb.AppendFormat("\n  reduce: {0} {{{1}}}", c.AsReduction.Item, node.Action.Token);
                        else
                            foreach (ParserAutomatonItem item in c.AsShift.GetRelatedItems())
                                sb.AppendFormat("\n  shift: {0}", item);
                }
            }
            return sb.ToString().Trim();
        }
    }
}
